package net.funtip.ormgenerator.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import net.funtip.ormgenerator.util.Tools;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Entity {
	
	private Logger log = LoggerFactory.getLogger(this.getClass());
	
	private String name;
	private String packageName;
	private String tableName;
	private String columnPrefix;
	private Model model;
	private Map<String, Attribute> attributes;
	private List<Method> methods;
	private List<String> implementedInterfaces;
	private Set<String> importedTypes;
	private boolean serializable;
	private boolean abstractSuperclass;
	private Entity superClass;
	private List<UniqueConstraint> uniqueConstraints;
	private List<Finder> finders;
	
	public Entity(String packageName, String name, Model model) {
		this.name = name;
		this.packageName = packageName;
		this.model = model;
		model.addEntity(this);
		attributes = new HashMap<String, Attribute>();
		methods = new ArrayList<Method>();
		importedTypes = new HashSet<String>();
		implementedInterfaces = new ArrayList<String>();
		uniqueConstraints = new ArrayList<UniqueConstraint>();
		finders = new ArrayList<Finder>();
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getDaoName() {
		return getName() + "Dao";
	}
	
	public String getJpaName() {
		return getName() + "JpaImpl";
	}
	
	public String getObjectifyName() {
		return getName() + "ObjectifyImpl";
	}
	
	public String getJpaDaoName() {
		return getName() + "DaoBaseJpaImpl";
	}
	
	public String getObjectifyDaoName() {
		return getName() + "DaoObjectifyImpl";
	}
	
	public String getEntityPackageName() {
		if (packageName != null) {
			return packageName;
		} else {
			return model.getBasePackageName()+".entity";
		}
	}
	
	public String getDaoPackageName() {
		if (packageName != null) {
			return packageName + ".dao";
		} else {
			return model.getBasePackageName()+".dao";
		}
	}
	
	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}
	
	public String getTableName() {
		if (tableName == null || tableName.isEmpty()) {
			tableName = generateTableName(name);
		}
		return tableName;
	}
	
	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public List<Attribute> getAllAttributes() {
		List<Attribute> l = new ArrayList<Attribute>();
		l.addAll(attributes.values());
		if (superClass != null) {
			l.addAll(superClass.getAllAttributes());
		}
		return l;
	}
	
	public List<Attribute> getAttributes() {
		List<Attribute> l = new ArrayList<Attribute>();
		l.addAll(attributes.values());
		return l;
	}
	
	public List<Attribute> getAttributesWithDefaults() {
		List<Attribute> l = new ArrayList<Attribute>();
		for (Attribute a: getAllAttributes()) {
			if (a.getDefaultValue() != null) {
				l.add(a);
			}
		}
		return l;
	}

	public void addAttribute(Attribute a) {
		attributes.put(a.getName(), a);
		parseTypes(a.getType());
	}

	public Attribute getAttribute(String name) {
		Attribute a = attributes.get(name);
		if (a == null && superClass != null) {
			a = superClass.getAttribute(name);
		}
		return a;
	}
	
	private void parseTypes(String type) {
		StringTokenizer stok = new StringTokenizer(type, "<>[], ");
		while (stok.hasMoreElements()) {
			String t = stok.nextToken();
			addImportedType(t);
		}
	}

	public Model getModel() {
		return model;
	}

	@Override
	public String toString() {
		return "Entity [packageName=" + packageName + ", name=" + name + "]";
	}

	public Set<String> getImportedTypes() {
		return importedTypes;
	}

	public void addImportedType(String type) {
		log.info("adding imported type "+type);
		if (type == null || type.trim().isEmpty() || type.indexOf(".") == -1) return;
		importedTypes.add(type);
	}

	public boolean isSerializable() {
		return serializable;
	}

	public void setSerializable(boolean serializable) {
		this.serializable = serializable;
		if (serializable) {
			addImplementedInterface("java.io.Serializable");
		}
	}

	public List<String> getImplementedInterfaces() {
		List<String> l = new ArrayList<String>();
		l.addAll(implementedInterfaces);
		if (superClass != null) {
			l.add(superClass.getName());
		}
		return l;
	}

	public void addImplementedInterface(String s) {
		addImportedType(s);
		implementedInterfaces.add(s);
	}
	
	public String getImplementedInterfacesList() {
		String r = "";
		for (String s: getImplementedInterfaces()) {
			if (!r.isEmpty()) {
				r += ", ";
			}
			r += Tools.getTypeNameWithoutPackage(s); 
		}
		return r;
	}
	
	public String getColumnPrefix() {
		if (columnPrefix == null || columnPrefix.isEmpty()) {
			String s = getName();
			columnPrefix = "";
			for (int index=0; index < s.length(); index++) {
				String letter = s.substring(index, index+1);
				if (letter.equals(letter.toUpperCase())) {
					columnPrefix += letter.toUpperCase();
				}
			}
			if (columnPrefix != null && !columnPrefix.isEmpty() && !columnPrefix.endsWith("_")) {
				columnPrefix += "_";
			}
		}
		return columnPrefix;
	}

	public void setColumnPrefix(String columnPrefix) {
		this.columnPrefix = columnPrefix;
	}

	protected String generateTableName(String s) {
		String tabName = "";
		boolean lastLetterLowerCase = false;
		for (int index=0; index < s.length(); index++) {
			String letter = s.substring(index, index+1);
			if (letter.equals(letter.toUpperCase())) {
				if (index > 0 && lastLetterLowerCase) {
					tabName += "_";
				}
			}
			tabName += letter.toUpperCase();
			lastLetterLowerCase = letter.equals(letter.toLowerCase());
		}
		// make the noun plural
		if (!s.toUpperCase().endsWith("Y") && !s.toUpperCase().endsWith("S")) {
			tabName += "S";
		}
		return tabName;
	}
	
	public boolean isImplementsInterfaces() {
		return implementedInterfaces.size() > 0;
	}

	public boolean isMandatoryAttributes() {
		return getMandatoryAttributeList().size() > 0;
	}
	
	public List<Attribute> getMandatoryAttributeList() {
		List<Attribute> l = new ArrayList<Attribute>();
		for (Attribute a: getAllAttributes()) {
			if (a.isMandatory()) {
				l.add(a);
			}
		}
		return l;
	}

	public boolean isAbstractSuperclass() {
		return abstractSuperclass;
	}

	public void setAbstractSuperclass(boolean abstractSuperclass) {
		this.abstractSuperclass = abstractSuperclass;
	}

	public List<Method> getMethods() {
		return methods;
	}

	public void addMethod(Method method) {
		methods.add(method);
	}

	public Entity getSuperClass() {
		return superClass;
	}

	public void setSuperClass(Entity superClass) {
		this.superClass = superClass;
	}

	public List<UniqueConstraint> getUniqueConstraints() {
		return uniqueConstraints;
	}

	public void addUniqueConstraint(UniqueConstraint uc) {
		this.uniqueConstraints.add(uc);
	}

	public List<Finder> getFinders() {
		return finders;
	}

	public void addFinder(Finder finder) {
		this.finders.add(finder);
	}

	public Attribute getIdentifier() {
		Attribute identifier = null;
		for (Attribute a: getAllAttributes()) {
			if (a.isIdentifier()) {
				if (identifier != null) {
					String msg = "Entity has multiple identifiers: "+identifier+", "+a;
					log.error(msg);
					throw new RuntimeException(msg);
				}
				identifier = a;
			}
		}
		return identifier;
	}
	
	public List<Attribute> getEqualsAttributes() {
		List<Attribute> l = new ArrayList<Attribute>();
		for (Attribute a: getAllAttributes()) {
			if (a.isIncludedInEquals()) {
				l.add(a);
			}
		}
		return l;
	}
	
	public List<Attribute> getHashCodeAttributes() {
		return getEqualsAttributes();
	}

	public String getPrimaryKeyName() {
		StringBuffer sbuf = new StringBuffer();
		sbuf.append("PK_");
		sbuf.append(getTableName().toUpperCase());
		return sbuf.toString();
		
	}
}
